iT邦幫忙

第 11 屆 iThome 鐵人賽

0
自我挑戰組

練習程式系列 第 37 篇

C# 筆記

  • 分享至 

  • xImage
  •  

1 程式來源:async vs thread

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Hello World! Main Thread:  {Thread.CurrentThread.ManagedThreadId }");
            AnotherMethod();
            Console.WriteLine($"Finish AnotherMethod():  {Thread.CurrentThread.ManagedThreadId }");
           // Console.Read();
        }

        static async void AnotherMethod()
        {
            await Task.Delay(2000);
            Console.WriteLine($"async await complete {Thread.CurrentThread.ManagedThreadId }");
        }
    }

結果:
https://ithelp.ithome.com.tw/upload/images/20200316/20111994RT8GxO2YTk.png

執行不到這行:

Console.WriteLine($"async await complete {Thread.CurrentThread.ManagedThreadId }");

按F11 逐行執行, 大概是因為 await Task.Delay(2000); 時 ,程式又回到
static void Main(string[] args) 裡面 , 所以印完Finish AnotherMethod() ,程式就結束了。

所以如果多加Console.Read();,因為程式還沒結束 , 所以就可以印出:

Console.WriteLine($"async await complete {Thread.CurrentThread.ManagedThreadId }");

結果:
https://ithelp.ithome.com.tw/upload/images/20200316/20111994AROBffTiBn.png

然後按F11 逐行執行,會發現用async、await ,系統還是會創建額外的執行緒(是因為await 這行 執行完後,為了要提醒 我完成了, 所以開了一個Worker Thread ? 但是這是非常輕量的thread ?)(async await的thread):
https://ithelp.ithome.com.tw/upload/images/20200316/20111994ZXCy941maj.png

自己創建一個Thread,會跟async、await系統創建的,不太一樣:
https://ithelp.ithome.com.tw/upload/images/20200316/20111994LUbBc3X1Y1.png

程式async await跑五次迴圈:

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Hello World! Main Thread:  {Thread.CurrentThread.ManagedThreadId }");
            for (int i = 0; i < 5; i++)
           {
                Console.WriteLine($"i:  {i }");
                AnotherMethod();
            }
            Console.WriteLine($"Finish AnotherMethod():  {Thread.CurrentThread.ManagedThreadId }");
            Console.Read();
        }

        static async void AnotherMethod()
        {
            Console.WriteLine($"async await not complete {Thread.CurrentThread.ManagedThreadId }");
            await Task.Delay(20000);
            Console.WriteLine($"async await complete {Thread.CurrentThread.ManagedThreadId }");
        }
    }

這邊應該是真的有多條執行緒同步再跑嗎?,跟自己的電腦是幾核心有關係?,因為大概等20秒左右,五個async await complete就全部跳出來了,所以這4、7、8、5、6是多核平行跑,還是單核多個執行緒?(猜測async await這種系統的應該是單核多個執行緒,自己開的應該是多核平行跑,還是要看cpu決定?)
結果:

Hello World! Main Thread:  1
i:  0
async await not complete 1
i:  1
async await not complete 1
i:  2
async await not complete 1
i:  3
async await not complete 1
i:  4
async await not complete 1
Finish AnotherMethod():  1
async await complete 4
async await complete 7
async await complete 8
async await complete 5
async await complete 6

程式thread跑五次迴圈:

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Hello World! Main Thread:  {Thread.CurrentThread.ManagedThreadId }");
             for (int i = 0; i < 5; i++)
             {
                Console.WriteLine($"i:  {i }");
                Thread x = new Thread(AnotherMethod1);
                 x.Start();
             }
            Console.WriteLine($"Finish AnotherMethod1():  {Thread.CurrentThread.ManagedThreadId }");
            Console.Read();
        }

        static void AnotherMethod1()
        {
            Console.WriteLine($"Thread not complete {Thread.CurrentThread.ManagedThreadId }");
            Task.Delay(2000);
            Console.WriteLine($"Thread complete {Thread.CurrentThread.ManagedThreadId }");
        }
    }

結果:

Hello World! Main Thread:  1
i:  0
i:  1
i:  2
i:  3
i:  4
Thread not complete 4
Thread not complete 6
Thread not complete 5
Finish AnotherMethod1():  1
Thread not complete 8
Thread not complete 9
Thread complete 4
Thread complete 6
Thread complete 5
Thread complete 8
Thread complete 9

參考:ASP.NET async 基本心法
看到:

.NET 實現非同步作業的做法是在需要等待時建立 SynchronizationContext,讓執行緒中斷作業先去處理其他作業,等待結束後再回頭繼續後半段。而繼續執行作業的執行緒可以是當初建立 SynchronizationContext 的那一條執行緒,也可以是其他執行緒。

換言之,若是一堆吃 CPU 的作業,增加執行緒肯定可以加速;若為要等待 I/O 回應的作業,增加執行緒用處不大,改為非同步作業才算對症下藥。

整理:
一 async await 是非同步作業,用來處理http的request、response,用的到

二 所以 > await Task.Delay(2000);
在await之後的程式 ,可能會新開執行緒,或是不開執行緒(根據 Task.Delay(2000) ,Task.Delay(2000)有開就會開新執行緒), 然後程式不會就在那邊等待 ,而是會跑回static void Main(string[] args),
完成後續的程式,在回到await之後的程式去跑

基本上對這塊一知半解,目前只會寫程式觀察執行順序,或是看看工作管理員。
找到教學,接著來練習:
新書發布: .NET 本事-非同步程式設計

程式來源:
async-book-support/Examples/Ch02/Ex10_Task/Program.cs

程式:

namespace ThreadTest03
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 1000; i++)
            {
                Thread t1 = new Thread(MyTask);
                t1.Start();
            }
        }

        private static void MyTask()
        {
            Thread.Sleep(100000);
        }

    }

}

這樣寫好像真的會開1000個執行緒 , 到工作管理員的詳細資料 -->右鍵 選取欄位 -- > 增加
執行緒 -- >找到 ThreadTest03.exe ,發現有1010個執行緒 ,記憶體是 46184 K
https://ithelp.ithome.com.tw/upload/images/20200522/20111994MGA0Q4ZfRH.png

然後可以在寫一個這樣的程式:

    class Program
    {
        static void Main(string[] args)
        {
            Console.ReadLine();
        }
    }

發現有10個執行緒,記憶體1856KB:
https://ithelp.ithome.com.tw/upload/images/20200522/201119941znGQ8ztii.png

所以 1010 - 10 =1000 ; 46184 -1856 = 44328
所以真的有開1000個執行緒? , 然後 一個執行緒大概4430K ?(這樣的方法好像不是很正確)
不過很多教學都寫一條執行緒1MB,所以用這方法推論應該是錯的

然後參考:
[c#]執行緒(thread)和執行緒集區(threadpool)的使用

t1.Abort(); 可以關執行緒
t1.IsAlive 檢查執行緒是否還活著

但是關掉一個執行緒,也需要一點時間,所以關掉後馬上檢查是否還活著,會發現執行緒還活著:

    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 1000; i++)
             {
                 Thread t1 = new Thread(MyTask);
                 t1.Start();
                 //Thread.Sleep(5);
                 t1.Abort();
                if (t1.IsAlive)//判斷是否還活著
                {
                    Console.WriteLine("t1 Alive");
                }
            }
            Console.ReadLine();
        }

        private static void MyTask()
        {
            Thread.Sleep(100000);
        }

    }

結果一堆t1 Alive ,但是把Thread.Sleep(5);的註解拿掉 , 應該就少很多t1 Alive了

2 WPF http get async 練習

程式來源:
C# Async / Await - Make your app more responsive and faster with asynchronous programming

平行運算 (一):Parallel.For、Parallel.Foreach 用法及技巧
How can I tell when HttpClient has timed out?
How to correctly write Parallel.For with async methods
HTTP Get and Post request in C#.net

練習三種按鈕,request 網址

1 async await 按鈕,但是是一個request結束後,才執行另一個request,所以跟3比,比較慢
畫面:
https://ithelp.ithome.com.tw/upload/images/20200316/20111994feSdbQhC9N.png

2 沒有async await 的按鈕 (時間先跑出來了)
畫面:
https://ithelp.ithome.com.tw/upload/images/20200316/201119942Ph45qPFGl.png

3 Task.WhenAll (看起來只是把很多個task聚在一起await,但是相比1速度快很多,因為1 是 一個request完成後,再 另一個 request,這個是 await 多個執行緒全部request完成。)
畫面:
https://ithelp.ithome.com.tw/upload/images/20200316/20111994GaaHuAwyh7.png

程式:
MainWindow.xaml.cs:

<Window x:Class="EasyGet.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:EasyGet"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="450"
        Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>


        <Button Grid.Row="0"
                Click="get_Click01">Async Execute</Button>

        <Button Grid.Row="1"
                Click="get_Click02">Normal Execute</Button>

        <Button Grid.Row="2"
                Click="get_Click03">Parallel Async Execute</Button>
        
        <TextBlock x:Name="resultsWindow"
                   Grid.Row="3"
                   Margin="10" />
    </Grid>
</Window>

MainWindow.xaml:

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;


namespace EasyGet
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        //async
        private async void get_Click01(object sender, RoutedEventArgs e)
        {
            resultsWindow.Text = " ";
            var watch = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < 10; i++)
            {
                await get_Http("https://ithelp.ithome.com.tw/");
            }
            watch.Stop();
            var elapsedMs = watch.ElapsedMilliseconds;
            resultsWindow.Text += $"Total execution time: { elapsedMs }" + Environment.NewLine;
        }

        //http async ,但是按鈕點擊沒有 async
        private void get_Click02(object sender, RoutedEventArgs e)
        {
            resultsWindow.Text = " ";
            var watch = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < 10; i++)
            {
                get_Http("https://ithelp.ithome.com.tw/");
            }
            watch.Stop();
            var elapsedMs = watch.ElapsedMilliseconds;
            resultsWindow.Text += $"Total execution time: { elapsedMs }" + Environment.NewLine;
        }

        //async  全部http request
        private async void get_Click03(object sender, RoutedEventArgs e)
        {
            resultsWindow.Text = " ";
            var watch = System.Diagnostics.Stopwatch.StartNew();
            List<string> myLists = new List<string>(); //要request的資料
            for (int i = 0; i < 10; i++)
            {
                myLists.Add("https://ithelp.ithome.com.tw/");
            }

            List<Task> TaskLists = new List<Task>(); //task List
            foreach (string data in myLists)
            {
                TaskLists.Add(get_Http(data));
            }

            await Task.WhenAll(TaskLists);

            watch.Stop();
            var elapsedMs = watch.ElapsedMilliseconds;
            resultsWindow.Text += $"Total execution time: { elapsedMs }" + Environment.NewLine;
        }

        private async Task get_Http(String url)
        {
            using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromMilliseconds(3000) })
            {
                try
                {
                    using (HttpResponseMessage response = await client.GetAsync(url))
                    {
                        using (HttpContent content = response.Content)
                        {
                            string mycontent = await content.ReadAsStringAsync();
                            resultsWindow.Text += $"{ mycontent.Length }" + Environment.NewLine;
                        }
                    }
                }
                catch (WebException ex) //還不知道這是什麼
                {
                    resultsWindow.Text += $"WebException: { ex }" + Environment.NewLine;
                }
                catch (TaskCanceledException ex) //timeout事件
                {
                    resultsWindow.Text += $"TaskCanceledException: { ex }" + Environment.NewLine;
                }
            }
        }


    }
}

備註:
參考:What is TPL ( Task Parallel Library) and how it differs from threads (c# interview questions) ?
windows + R -- > perfmon (開啟效能監視器) -- > 右鍵新增計數器 -- >選擇CPU核心
-- >可以觀看每個CPU核心的執行狀況?
https://ithelp.ithome.com.tw/upload/images/20200316/20111994P6IPLDLPZF.png

3 Delegates

程式來源:C# Delegates explained
Delegates練習:

using System;

namespace DelegatesTest
{
    class Program
    {

        public delegate void TestMethodPtr();

        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            TestMethodPtr obj = new TestMethodPtr(TestMethod);
            obj.Invoke();

            TestClass obj1 = new TestClass();
            obj1.LongRunning(TestMethod1);
        }

        static void TestMethod()
        {
            Console.WriteLine("Hello TestMethod()");
        }

        static void TestMethod1(int i)
        {
            Console.WriteLine($"TestMethod1 {i}");
        }
    }

     class TestClass
    {
        internal delegate void CallBack(int i);
        internal void LongRunning(CallBack obj)
        {
            for (int i = 0; i < 1000; i++)
            {
                obj(i);
            }
        }
    }
}

4 Itextsharp

iTextSharp是舊的,現在是itext7,不過不會用itext7,就用iTextSharp

參考資料:
iTextSharp
itextsharp/src/core/iTextSharp/text/pdf/parser/LocationTextExtractionStrategy.cs
[C#][iTextSharp] PDF 套版
Getting Coordinates of string using ITextExtractionStrategy and LocationTextExtractionStrategy in Itextsharp
[.NET,iTextSharp]用程式呼叫cmd(command line)執行exe(以開啟adobe reader列印pdf為例)
Search Particular Word in PDF using Itextsharp
ItextSharp can't find PDF
[.NET]iTextsharp找不到字型檔問題(.ttf not found as file or resource)

itext7:
itext7
itext7-dotnet

iTextSharp整理

1 座標以PDF左下為(0,0),x往右+,y往上+

2 LocationTextExtractionStrategy 可以取得特定文字的座標,這樣可以把PDF的字用白色,然後在上面自己在增加圖片或文字。

3
這段可以取得當前PDF頁數(像是第一頁)的所有文字,
currentPageText -->有所有文字的內容

ITextExtractionStrategy strategy = new SimpleTextExtractionStrategy();
string currentPageText = PdfTextExtractor.GetTextFromPage(pdfReader, page, strategy);

5 dll

Possible to decompile DLL written in C?
[工具]反組譯的好工具-JustDecompile
[.NET] ILSpy 程式碼反組譯工具
ILSpy 6.0 Preview 2
C#_DLL反編譯功能_EXE(Application)和DLL(Application Extension)的差別在哪???_DLL的意義_反編譯目的

6 Parallel.For

C# Parallel.For 的效果威力
What is TPL ( Task Parallel Library) and how it differs from threads (c# interview questions) ?
如何:撰寫簡單的 Parallel.For 迴圈

7 WinSCard

Developing Simple APDU Sender using WinSCard
C# SCardGetStatusChange() check smartcard status
python 讀取健保IC卡資訊
What APDU command gets card ID
[Java] Java讀取健保卡基本資料(晶片讀卡機)
[C#] C# 語取健保卡基本資料─晶片讀卡機
整理:

1 WinSCard在哪?

C:\Windows\System32\WinSCard.dll

2 目前整個流程是:

SCardEstablishContext -- > SCardListReaders (找到所有讀卡機) -- > 用一個 List 把讀卡機的名稱都存起來 -- > 用List 裡的所有讀卡機的名稱 ,製作一個 SmartCardReaderState[] 的東西 -- >
SCardGetStatusChange 把 SmartCardReaderState[] 當參數帶入 --> 之後 跑 SmartCardReaderState[] 迴圈,一個一個檢查 卡片狀態 -- > 如果卡片狀態是Card Present 的 , 就用 SCardConnect 連接卡片
-- > 連接卡片成功的話,就可以SCardTransmit 送訊息給智慧卡 -- >SCardTransmit的 倒數第二個參數 ref byte pbRecvBuffer 就可以獲得 智慧卡給的資訊 -- >接著關閉連接卡片 SCardDisconnect -- >結束SCardReleaseContext

每個過程如果 回傳 的整數 != 0 ,就代表有錯誤

3

智慧卡應用協議數據單元

有兩種類別的APDU:命令APDU和響應APDU。
命令APDU由讀卡器發送到智慧卡-它包含了一個必選的4字節的頭部(CLA,INS,P1,P2)和0到255位元組的數據。
響應APDU由智慧卡發送到讀卡器-它包含了必選的2位元組的狀態字和0到256位元組的數據。

The SCardTransmit function sends an APDU command to the smart card and expects to receive APDU response from the card.

所以SCardTransmit應該就是 命令APDU 送訊息給智慧卡 ,智慧卡會在帶訊息回參數

4

這段程式的讀卡機型號好像是 EZ100PU
EZ100PU

不過測試是 這個也可以it-500u:
it-500u

5

判斷讀卡機的卡片狀態主要就是參考這段:
C# SCardGetStatusChange() check smartcard status

不過很多看不懂,大概只知道

readerState.eventState 去跟
public static readonly UInt32 Present = 0x00000020; //十進位就是32 // 二進位100000
public static readonly UInt32 Atrmatch = 0x00000040; // 十進位64 // 二進位1000000
用&運算,如果其中一個>0 ,就算 "有卡片在 讀卡機"

如果卡片晶片放錯邊,好像也算 "有卡片在 讀卡機"
不過卡片放錯邊,會在SCardConnect有錯誤 ,錯誤數字: -2146434970 ,不知道是不是數字都一樣

6

獲得的讀卡機字串長這樣,表示有兩個讀卡機:
Generic Smart Card Reader Interface 0 Generic Usb Smart Card Reader 0
然後結尾都是以0當結尾, 所以用0 split

7

裡面常看到in、out、ref:
C#雜記 — 參數修飾詞 in、out、ref
C# - Using in, out, and Ref with Parameters

整理
1 out 一定要new ,不然會有:

在程式控制權脫離目前的方法之前,必須指派 out 參數 'student'。

2
in的參數 ,則是 不能有new:

無法指派至 變數 'in Program.Student',因為它不是唯讀變數

沒辦法:

student = new Student();

但是可以

student.Enrolled = true;

因為

僅有物件本身(student)為唯獨,物件內的屬性(student.Enrolled )不受影響,因為是不同的參考位址

3
ref 就是 沒有限制

8

Smartcard 讀寫器函數庫的使用說明

參考:

1

SCardListReaders 第一次呼叫時並不知道機器有幾台可用之讀卡機所以mszReaders先代入NULL,取得實際數量後再呼叫一次就可列出讀卡機名稱。

所以這部分這樣寫:

ErrorInt = SCardListReaders(ContextHandle, null, null, out bufferLength); //先取得長度
Debug.WriteLine($"bufferLength{bufferLength}"); //71
ReaderList = new byte[bufferLength];
ErrorInt = SCardListReaders(ContextHandle, null, ReaderList, out bufferLength);

9

常看到錯誤6
可能原因:
System Error Codes (0-499)

ERROR_INVALID_HANDLE
6 (0x6)
The handle is invalid.

Error 6 when connecting Gemalto smart card

可能錯誤原因:

Try to check your OS (32-bit or 64-bit) and your smart card driver (32-bit or 64-bit). Ensure that they are the same. Also, check that your codes are using the correct variable type.

一 smart card driver要跟作業系統一致32-bit or 64-bit

二 查看[DllImport("WinScard.dll")] 裡的變數有沒有什麼錯誤

三
還不了解:
Buyer Beware: Change Smart Card Admin Key PIN

Is it possible to update admin key by apdu command ?
Usually the admin key is protected with checksum, proprietary CLA and INS, or specific APDUs sequence. Please note that this depends on the vendor implementation which is RARELY shared.

四
WinScard SCardConnect returns SCARD_E_NOT_READY

SCardConnect 有個參數是 uint dwShareMode,設定的值有

// This application will NOT allow others to share the reader. (SCARD_SHARE_EXCLUSIVE)
Exclusive = 0x0001,
// This application will allow others to share the reader. (SCARD_SHARE_SHARED)
Shared = 0x0002,
// Direct control of the reader, even without a card. (SCARD_SHARE_DIRECT)
Direct = 0x0003

如果設定1 ,開兩個程式去讀 卡片 ,就會有錯誤 。 所以改成2

3的話好像也有錯誤-2146435061 。所以如果有兩個程式同時去跑讀卡,都設定2才不會有誤

五
SCardGetStatusChange 這邊如果是在netcoreapp3.1 ,也會有6 ,在.NETFramework,Version=v4.7.2不會

10

SCardEstablishContext 程式一開始執行一次
SCardReleaseContext 程式關掉前執行一次

Circular Progressbar

想在WPF用個Circular Progressbar

一開始參考:
Circular Progressbar using C#

是在Windows Forms 畫的 , 會看起來閃閃的 , 所以不採用。
如何在WPF 使用Windows Forms 的東西?

一 增加System.Windows.Forms 、 WindowsFormsIntegration (加入參考,原本就有的東西,不用下載)

二 在 xaml增加

        <WindowsFormsHost x:Name="windowsFormsHost"
                          VerticalAlignment="Center"
                          Height="500"
                          Width="500"
                       >
            <WindowsFormsHost.Child>
                <wf:Panel x:Name="myPanel"
                          Paint="CircularProgressBar_Paint" />
            </WindowsFormsHost.Child>
            
        </WindowsFormsHost>

三 之後再CircularProgressBar_Paint 就可以畫圈圈了

後來找有沒有現成的套件:
C# Tutorial - Circle Progress Bar | FoxLearn
https://ithelp.ithome.com.tw/upload/images/20200614/20111994AVYNPjopix.png

不過我不會在WPF使用,所以就放棄了

最後參考這個,完成的
How To Create A ProgressCircle In C# With WPF

步驟:

1
好像是要先下載這個東西(youtube有附連結):
Microsoft Expression Blend 4 Software Development Kit (SDK) for .NET 4
https://www.microsoft.com/zh-tw/download/confirmation.aspx?id=10801

然後下載的dll會在

C:\Program Files (x86)\Microsoft SDKs\Expression\Blend\.NETFramework\v4.0\Libraries\Microsoft.Expression.Drawing.dll

2
之後這邊作者有提供code,在youtube有附連結 , 只要加入Microsoft.Expression.Drawing.dll的參考,就可以跑程式了。

3
Cannot find invalidate method for MainWindow

invalidate -- Windows Forms
InvalidateVisual() -- WPF
先用InvalidateVisual()吧!還不知道RedrawWindow function是什麼

4
修改ProgressCircle 的 value 值 1 -100 ,就有轉圈圈的效果

測試

[.NET]iTextsharp找不到字型檔問題(.ttf not found as file or resource)
【python內建模組- unitest】單元測試
Creating your first unit test


上一篇
Android,Navigation
系列文
練習程式 共 37 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
小碼農米爾
iT邦高手 1 級 ‧ 2020-03-16 13:56:59

await 之後不一定會在新的執行緒

要看後面接的方法裡面有沒有另外開

比較好的說法是 Task.Delay(2000) 這個方法裡面會開新的執行緒

喔喔,了解,謝謝

我要留言

立即登入留言